﻿using System.Net;
using System.Security.Cryptography.X509Certificates;
using HelenServer.Authentication.OAuth2;
using HelenServer.UserCenter.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Logging;

namespace Microsoft.Extensions.DependencyInjection;

public static class ServiceConllectionExtensions
{
    public static IIdentityServerBuilder AddCloudIdentityServer(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddHttpContextAccessor();
        services.AddOptions<CredentialsOptions>()
            .Configure<IConfiguration>((opts, cfg) =>
            {
                cfg.Bind(CredentialsOptions.Position, opts);
            });
        services.AddHttpClient<OAuthEndpointService>().ConfigurePrimaryHttpMessageHandler(provider =>
        {
            return new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = delegate { return true; },
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
            };
        });

        var builder = services
            .AddIdentityServer(options =>
            {
                options.Authentication.CookieAuthenticationScheme = "helen_cookie";
                configuration.Bind(CredentialsOptions.Position, options);
            })
            .AddClientStore<ClientStore>()
            .AddResourceStore<ResourceStore>()
            .AddPersistedGrantStore<PersistedGrantStore>()
            .AddProfileService<UserProfileService>();

        builder.AddResourceOwnerValidator<UserValidator>();

        var certFile = configuration.GetSection("IdentityServer4:CertFile");

        if (certFile.Exists())
        {
            var credential = X509Certificate2.CreateFromPemFile(
                certFile.GetValue<string>("Path"),
                certFile.GetValue<string>("KeyPath"));

            builder.AddSigningCredential(credential);
        }
        else
        {
            builder.AddDeveloperSigningCredential();
        }

        services.AddAuthentication(opts =>
        {
            opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            opts.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opts =>
        {
            opts.BackchannelHttpHandler = new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
            };
            opts.Authority = configuration["IdentityServer4:Authority"];
            opts.RequireHttpsMetadata = !string.IsNullOrWhiteSpace(opts.Authority) &&
                opts.Authority.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
            opts.TokenValidationParameters.ValidateAudience = false;
            opts.SaveToken = true;
            opts.IncludeErrorDetails = true;

            IdentityModelEventSource.ShowPII = true;
        })
        .AddCookie("helen_cookie");

        return builder;
    }

    public static IApplicationBuilder UseCloudIdentityServer(this IApplicationBuilder app)
    {
        app.Use((context, next) =>
        {
            var configuration = context.RequestServices.GetRequiredService<IConfiguration>();

            var forwardOptions = context.RequestServices.GetRequiredService<IOptions<ForwardedHeadersOptions>>();

            if (context.Request.Headers.ContainsKey(forwardOptions.Value.OriginalHostHeaderName))
            {
                var basePath = $"/{configuration["Swagger:SwaggerUrlPrefix"]}";

                context.Request.PathBase = new PathString(basePath);
            }

            return next();
        });

        app.UseIdentityServer();

        return app;
    }

    internal const SameSiteMode Unspecified = (SameSiteMode)(-1);

    public static IServiceCollection AddNonBreakingSameSiteCookies(this IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(opts =>
        {
            opts.MinimumSameSitePolicy = SameSiteMode.Unspecified;
            opts.Secure = CookieSecurePolicy.Always;
            opts.OnAppendCookie = ctx => CheckSameSite(ctx.Context, ctx.CookieOptions);
            opts.OnDeleteCookie = ctx => CheckSameSite(ctx.Context, ctx.CookieOptions);
        });

        return services;
    }

    private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None)
        {
            var userAgent = httpContext.Request.Headers["User-Agent"].ToString();

            if (DisallowsSameSiteNone(userAgent))
            {
                options.SameSite = Unspecified;
            }
        }
    }

    private static bool DisallowsSameSiteNone(string userAgent)
    {
        return userAgent.Contains("CPU iPhone OS 12") ||
            userAgent.Contains("iPad; CPU OS 12") ||
            userAgent.Contains("Safari") &&
            userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
            userAgent.Contains("Version/") ||
            userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6");
    }
}